/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Protocol dispatcher for GIMnetAP communication - implementation file.
 * \author Antti Maula <antti.maula@tkk.fi>
 */
#include "protocolDispatcher.h"
#include "ownutils.h"
#include "owndebug.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>

//*****************************************************************************
//*****************************************************************************
static void defaultProtocolDispatcher(int protocol_number,
                                      const unsigned char *data,
                                      int size,
                                      void *userPtr);
static int readInput(TProtocolDisp *pd, int timeout);
//*****************************************************************************
//*****************************************************************************

int protocolDispInit(TProtocolDisp *pd)
{
  int i;
  
  // Initialize Connection module
  //  connectionInit(&pd->link);
  bufferInit(&pd->data_in);

  // Register the default handler for each and every protocol number
  for(i=0; i < MAX_PROTOCOL_COUNT; i++) {
    pd->protocolDispatcherTable[i] = defaultProtocolDispatcher;
    pd->protocolDispatcherUserPtrTable[i] = NULL;
  }

  // Return OK (currently; nothing is checked ;)
  return PROTOCOLDISPATCHER_OK;
}
//*****************************************************************************

void protocolDispDeinit(TProtocolDisp *pd)
{
  connectionDeinit(&pd->link);
  bufferDeinit(&pd->data_in);
}
//*****************************************************************************

int protocolDispConnect(TProtocolDisp *pd, 
                        const char *hubaddr,
                        int hubport)
{
  TConnectionTCP *tcpc = NULL;
  TConnectionUNIX *unixc = NULL;
  int r;

  if (hubport == PROTOCOLDISPATCHER_HUBPORT_UNIX) {
    // If Hub port is set to special port, use the hubaddr as UNIX Socket path
    //dPrintG(4,"Using UNIX Socket connection to '%s'", hubaddr);
    unixc = (TConnectionUNIX *)malloc(sizeof(TConnectionUNIX));
    r = connectionUNIXInit(unixc);
    r += connectionUNIXSetConnectionType(unixc, unixStyleClient, unixBlockingTypeBlocking); 
    r += connectionUNIXSetSocketFile(unixc, hubaddr);
    r += connectionInit(&pd->link, connectionUNIXGetConnectionPtrEntry(unixc));
    
  } else {
    // No special port set, so use the 'TCP' link.
    //dPrintG(4,"Using TCP Socket connection to '%s'", hubaddr);
    tcpc = (TConnectionTCP *)malloc(sizeof(TConnectionTCP));
    r = connectionTCPInit(tcpc);
    r += connectionTCPSetConnectionType(tcpc, tcpStyleClient, tcpBlockingTypeBlocking); 
    r += connectionTCPSetHost(tcpc, hubaddr); 
    r += connectionTCPSetPort(tcpc, hubport);
    r += connectionInit(&pd->link, connectionTCPGetConnectionPtrEntry(tcpc));
  
  }    

  r += connectionConnect(&pd->link);

  if (r != 0) {
    dPrintG(5,"Connection module returned ERROR for Connect!");
    connectionDeinit(&pd->link);
    return PROTOCOLDISPATCHER_ERROR; 

  } else {

    return PROTOCOLDISPATCHER_OK; 

  }

  return (r == 0) ? PROTOCOLDISPATCHER_OK : PROTOCOLDISPATCHER_ERROR; // 
}
//*****************************************************************************

int protocolDispDisconnect(TProtocolDisp *pd)
{
  // No particular error checking here.
  dPrintG(3,"Disconnecting ...");
  connectionDisconnect(&pd->link);
  return PROTOCOLDISPATCHER_OK;
}
//*****************************************************************************

int protocolDispRegisterCallback(TProtocolDisp *pd, 
                                 unsigned char protocol_number,
                                 protocolDispatcherPtr p,
                                 void *userPtr)
{
  // Just insert the function pointer to correct index in the table
  /*dPrintG(3,"Registered handler '%p' for protocol 0x%02x", 
    p, protocol_number); */
  pd->protocolDispatcherTable[protocol_number] = p;
  pd->protocolDispatcherUserPtrTable[protocol_number] = userPtr;
  return PROTOCOLDISPATCHER_OK;
}
//*****************************************************************************


int protocolDispProcess(TProtocolDisp *pd, int timeout)
{
  int result = 0, totalBytes;
  TProtocolIdentification *pid;

  // First; read data (if available) - and wait maximum of 'timeout' - ms.
  result = readInput(pd, timeout);

  // Now; Analyze the buffer
  // Run as long as there is enough data for the Identification Packet
  while (result >= 0 && bufferGetLength(&pd->data_in) > sizeof(TProtocolIdentification)) {
    // Make pointer
    pid = (TProtocolIdentification *)bufferGetPtr(&pd->data_in);
    
    // Take value of totalBytes
    totalBytes = pid->total_size;
    
    // Now, check whether we got enough data for the whole packet
    if (bufferGetLength(&pd->data_in) >= totalBytes) {
      // Whee, we got enough data.
      
      // Do callback
      pd->protocolDispatcherTable[pid->protocol_number](pid->protocol_number,
                                                        bufferGetPtr(&pd->data_in),
                                                        totalBytes,
                                                        pd->protocolDispatcherUserPtrTable[pid->protocol_number]);
      // Shift the buffer
      bufferShift(&pd->data_in, totalBytes); 
      
    } else {
      // Ok, Got enough data for header; but not for the payload, so break.
      break;
      
    }
  }
  
  return result<0;
}
//*****************************************************************************

int protocolDispSendPacket(TProtocolDisp *pd, const unsigned char *data, 
                           int dlen)
{
  assert(pd != NULL);
  assert(data != NULL);
  assert(dlen >= 0);

  // If the sent amount of bytes equals to requested amount, return OK;
  // otherwise return ERROR
  return (connectionSendData(&pd->link, data, dlen) == dlen) ? 
    PROTOCOLDISPATCHER_OK : PROTOCOLDISPATCHER_ERROR;
}
//*****************************************************************************

int protocolDispSendPacketEx(TProtocolDisp *pd, 
                             const unsigned char *header, 
                             int headerlen,
                             const unsigned char *payload,
                             int payloadlen)
{
  int result;

  // A few trivial checks
  assert(pd != NULL);
  assert(header != NULL);
  assert(headerlen >= 0);
  assert(payload != NULL);
  assert(payloadlen >= 0);
  
  // If the sent amount of bytes equals to requested amount, return OK;
  // otherwise return ERROR
  
  result = connectionSendData(&pd->link, 
                              header, headerlen);

  result += connectionSendData(&pd->link, 
                               payload, payloadlen);


  return (result == payloadlen+headerlen)  ?
    PROTOCOLDISPATCHER_OK : PROTOCOLDISPATCHER_ERROR;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

static void defaultProtocolDispatcher (int protocol_number,
                                       const unsigned char *data,
                                       int size,
                                       void *userPtr)
{
  fprintf(stderr,"Unhandled protocol:\n");
  fprintf(stderr,"\tProtocol number:\t0x%02x\n", protocol_number);
  fprintf(stderr,"\tPayload size:\t\t%d bytes\n", size);
}
//*****************************************************************************

static int readInput(TProtocolDisp *pd, int timeout)
{
  unsigned char buffer[4096*100];
  int result;

  // Read the data
  result = connectionReceiveDataTimed(&pd->link, 
                                      buffer, sizeof(buffer), 
                                      timeout);
  if (result > 0) {
    //dPrintG(10,"Got data! (%d bytes)", result);

    // Got bytes, stuff them in buffer
    bufferAppend(&pd->data_in, buffer, result);
    
  } else if (result == 0) {
    //dPrintG(15,"No new data available.");
    
  }

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************




